#pragma once
//----------------------------------------------------------------------------------------------------------------
//			Filename:		Oct.h
//			  Author:		Adrian De Barro
//			    Date:		12/08/2015
//----------------------------------------------------------------------------------------------------------------
//			  Status: REFACTORING
//----------------------------------------------------------------------------------------------------------------
#include <vector>
#include <queue>
#include <mutex>
#include <boost/thread/locks.hpp>
#include <boost/thread/shared_mutex.hpp>

#include "..\BVH.h"
#include "..\..\Integrators\RadianceDetails\RadiancePoint.h"
#include "..\..\CoreClasses\Point\Point3D\Point3D.h"
#include "..\..\Scene\Scene.h"
#include "..\..\CoreClasses\UtilClasses\Common.h"
#include "..\..\CoreClasses\BoundingShapes\BoundingBox.h"
#include "..\..\CoreClasses\Logger\Logger.h"
#include "Octree.h"

typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock > WriteLock;
typedef boost::shared_lock< Lock > ReadLock;
typedef boost::upgrade_lock<boost::shared_mutex> upgradableLock;
typedef boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock;

namespace Engine
{
	///-------------------------------------------------------------------------------------------------
	/// <summary>	A locking octree. </summary>
	///
	/// <remarks>	Adrian, 27/08/2015. </remarks>
	///-------------------------------------------------------------------------------------------------
	class LockingOctree
	{
	public:
		Lock m_myLock;
		std::mutex myMutex;
		
		
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: CONSTRUCTOR
		//		Return Value: VOID
		//		  Parameters: Scene*
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		LockingOctree(Engine::Scene* p_scene)
		{
			Engine::BVH * tempBvh = new BVH(p_scene->m_sceneObjects);
			m_rootNode = new OctNode(tempBvh->rootNode->boundingBox);
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: CONSTRUCTOR
		//		Return Value: string representation of a node
		//		  Parameters: BVH 
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		LockingOctree(BVH* p_sceneRepresentation)
		{
			m_rootNode = new OctNode(p_sceneRepresentation->rootNode->boundingBox);
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: CONSTRUCTOR
		//		Return Value: VOID
		//		  Parameters: VOID
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		LockingOctree()
		{
			m_rootNode = new OctNode();
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: InsertPoint
		//		Return Value: VOID
		//		  Parameters: RadiancePoint*
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		void InsertPoint(RadiancePoint* p_toInst)
		{
			InsertPoint(p_toInst, m_rootNode);
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: GetPoints
		//		Return Value: VOID
		//		  Parameters: RadiancePoint*, vector<RadiancePoints*>&
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		bool GetPoints(ShadeRec p_collisionDetails, Point3D p_normal, vector<RadiancePoint*>& p_availablePoints)
		{
			GetRadiancePoints(p_collisionDetails, p_normal, p_availablePoints);
			return p_availablePoints.size() > AMOUNT_OF_POINTS_FOR_ESTIMATE;
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: GetRadiancePoints
		//		Return Value: VOID
		//		  Parameters: RadiancePoint*, vector<RadiancePoints*>&
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		void GetRadiancePoints(ShadeRec p_sr, Point3D p_normalVector, vector<RadiancePoint*>& p_availablePoints)
		{
			OctNode* currentNode = m_rootNode;
			bool foundChildNode = false;
			while (!foundChildNode)
			{
				if (currentNode->childNode)
				{
					//------------------------------------READER LOCK HERE ----------------------------
					ReadLock r_lock(m_myLock);
					if (!currentNode->IsChildNode())
						goto BACK_TO_SEARCH;
					for (int pointIndex = 0; pointIndex <currentNode->m_storedPoints.size(); pointIndex++)
					{
						RadiancePoint* currentPoint = currentNode->m_storedPoints[pointIndex];
						double weight = currentPoint->GetWeighting(p_sr.localHitPoint , p_normalVector);
						if (weight > 0)
						{
							p_availablePoints.push_back(currentPoint);
						}
					}

					foundChildNode = true;
				}
				else
				{
					BACK_TO_SEARCH:
					bool nodeFound = false;
					for (int index = 0; index < 8; index++)
					{
						OctNode* currentSubNode = currentNode->children[index];

						if (currentSubNode->m_boundingBos->CheckPointInsideBox(p_sr.localHitPoint))
						{
							currentNode = currentSubNode;
							nodeFound = true;
							break;
						}

						//TODO might need to fix here
					}

					if (!nodeFound)
						foundChildNode = true;
				}
			}
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: CheckIfExist
		//		Return Value: VOID
		//		  Parameters: Point3D position
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		bool CheckIfExist(Point3D p_postion)
		{
			RadiancePoint* toFind = nullptr;
			return GetRadiancePoint(p_postion, toFind);
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: GetRadiancePoint
		//		Return Value: VOID
		//		  Parameters: Point3D position
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		bool GetRadiancePoint(Point3D p_position, RadiancePoint* p_outputRadiancPoint)
		{
			RadiancePoint* toFind = nullptr;
			OctNode * currentNode = m_rootNode;
			bool childNodeFound = false;
			while (!childNodeFound)
			{
				if (currentNode->IsChildNode())
				{
					//------------------------------------READER LOCK HERE ----------------------------
					ReadLock r_lock(m_myLock);
					if (!currentNode->IsChildNode())
						goto BACK_TO_SEARCH;
					for (int index = 0; index < currentNode->m_storedPoints.size(); index++)
					{

						RadiancePoint* currentRadiancePoint = currentNode->m_storedPoints[index];
						Point3D radiancePosition = currentRadiancePoint->m_position;
						if (currentRadiancePoint != nullptr && radiancePosition == p_position)
						{
							p_outputRadiancPoint = currentNode->m_storedPoints[index];
						}
					}
					childNodeFound = true;
				}
				else
				{
					BACK_TO_SEARCH:
					bool nextNode = false;

					for (int nodeIndex = 0; nodeIndex < 8; nodeIndex++)
					{
						if (currentNode->children[nodeIndex]->CheckIntersectionWithPoint(p_position))
						{
							currentNode = currentNode->children[nodeIndex];
							nextNode = true;
							break;
						}
					}

					if (!nextNode)
						childNodeFound = true;
				}
			}

			if (p_outputRadiancPoint == nullptr)
				return false;
			else
				return true;
		}

		RadiancePoint* GetRadiancePoint(Point3D p_position)
		{
			RadiancePoint* toFind = nullptr;
			OctNode * currentNode = m_rootNode;
			bool childNodeFound = false;
			while (!childNodeFound)
			{
				if (currentNode->IsChildNode())
				{
					//------------------------------------READER LOCK HERE ----------------------------
					ReadLock r_lock(m_myLock);
					if (!currentNode->IsChildNode())
						goto BACK_TO_SEARCH;
					for (int index = 0; index < currentNode->m_storedPoints.size(); index++)
					{

						RadiancePoint* currentRadiancePoint = currentNode->m_storedPoints[index];
						Point3D radiancePosition = currentRadiancePoint->m_position;
						if (currentRadiancePoint != nullptr && radiancePosition == p_position)
						{
							toFind = currentNode->m_storedPoints[index];
						}
					}
					childNodeFound = true;
				}
				else
				{
				BACK_TO_SEARCH:
					bool nextNode = false;

					for (int nodeIndex = 0; nodeIndex < 8; nodeIndex++)
					{
						if (currentNode->children[nodeIndex]->CheckIntersectionWithPoint(p_position))
						{
							currentNode = currentNode->children[nodeIndex];
							nextNode = true;
							break;
						}
					}

					if (!nextNode)
						childNodeFound = true;
				}
			}

			if (toFind != nullptr)
				return toFind;
			else
				return nullptr;
		}

		string GetStringRepresentation()
		{
			stringstream ss;
			GetStringRepresentation(ss, m_rootNode);
			return ss.str();
		}

		void GetStringRepresentation(stringstream &p_ss, OctNode* p_currentNode)
		{
			if (p_currentNode->IsChildNode())
			{
				vector<RadiancePoint*> pointsInNode = p_currentNode->m_storedPoints;

				for (int childrenIndex = 0; childrenIndex < pointsInNode.size(); childrenIndex++)
				{
					p_ss << "node->add(" + pointsInNode[childrenIndex]->ToString() + "),";
				}
			}
			else
			{
				p_ss << "node->split,";
				for (int nodeIndex = 0; nodeIndex < 8; nodeIndex++)
				{
					if (p_currentNode->children[nodeIndex]->IsChildNode())
					{
						vector<RadiancePoint*> pointsInNode = p_currentNode->children[nodeIndex]->m_storedPoints;

						for (int pointIndex = 0; pointIndex < pointsInNode.size(); pointIndex++)
						{
							p_ss << "child" + to_string(nodeIndex) + "->add("+pointsInNode[pointIndex]->ToString() + "),";
						}
					}
					else
					{
						p_ss << "child" + to_string(nodeIndex) + "->split,";
						GetStringRepresentation(p_ss, p_currentNode->children[nodeIndex]);
					}
				}
			}

		}

		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: DESTRUCTOR
		//		Return Value: VOID
		//		  Parameters: VOID
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		~LockingOctree()
		{
			delete m_rootNode;
		}

	private:
		/// <summary>	The root node. </summary>
		OctNode *m_rootNode;
		/// <summary>	The level. </summary>
		int level;
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: InsertPoint
		//		Return Value: VOID
		//		  Parameters: RadiancePoint*, OctNode*
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		void InsertPoint(RadiancePoint* p_toInst, OctNode* p_currentNode)
		{
			if (p_currentNode->IsChildNode())
			{
				//----------------------------------------------------------Writer Lock ---------------------------------------------
				upgradableLock w_lock(m_myLock);
				uniqueLock uniqueLock(w_lock);
				p_currentNode->InsertPoint(p_toInst);
			}
			else
			{
				try
				{
					bool outsideBounderies = false;
					
					for (int childIndex = 0; childIndex < 8; childIndex++)
					{
						OctNode* currentChildNode = p_currentNode->children[childIndex];
						if (currentChildNode->CheckIntersectionWithPoint(p_toInst))
						{
							outsideBounderies = true;
							InsertPoint(p_toInst, currentChildNode);
						}
					}
					
				}
				catch (const std::system_error& e)
				{
					cout << "problem with " << e.what() << endl;
				}
			}

		}
	};
	//----------------------------------------------------------------------------------------------------------------
}
//----------------------------------------------------------------------------------------------------------------
